<template>
    <view class="container page" :data-weui-theme="theme">
        <view>
            <canvas type="webgl" id="webgl" :style="'width: ' + width + 'px; height: ' + height + 'px'" @touchend="onTouchEnd">
                <cover-image src="/static/pages/rlsb/img/take_video_back.png" style="width: 100%; height: 700rpx; margin-top: 100rpx"></cover-image>
            </canvas>
        </view>
        <view class="page-body-text tc" style="height: 20%">
            提示：将摄像头对准人脸, 检测到的人脸将会被标记出识别框和面部标记点
            <view v-if="cameraPosition == 0">
                <button type="primary" @tap="switchCamera" data-value="1" :disabled="buttonDisable">切换为前置摄像头</button>
            </view>
            <view v-else>
                <button type="primary" @tap="switchCamera" data-value="0" :disabled="buttonDisable">切换为后置摄像头</button>
            </view>
        </view>
    </view>
</template>

<script>
import { createScopedThreejs } from 'threejs-miniprogram';
import { registerGLTFLoader } from './gltf-loader.js';
import yuvBehavior from './yuvBehavior.js';
const NEAR = 0.001;
const FAR = 1000;

//顶点着色器
var VSHADER_SOURCE =
    'attribute vec4 a_Position;\nvoid main(){\n' +
    '  gl_Position = a_Position;\n' +
    //将顶点坐标赋值给顶点着色器内置变量gl_Position
    '  gl_PointSize = 4.0;\n' +
    //设置顶点大小
    '}\n';

//片元着色器
var FSHADER_SOURCE =
    '#ifdef GL_ES\n precision mediump float;\n' +
    // 设置精度
    '#endif\n' +
    'varying vec4 v_Color;\n' +
    //声明varying变量v_Color，用来接收顶点着色器传送的片元颜色信息
    'void main(){\n' +
    '  float d = distance(gl_PointCoord, vec2(0.5, 0.5));\n' +
    //计算像素距离中心点的距离
    '  if(d < 0.5) {\n' +
    //距离大于0.5放弃片元，小于0.5保留片元
    '    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n' +
    '  } else { discard; }\n' +
    '}\n';

//初始化着色器函数
let initShadersDone = false;
function initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE) {
	console.log('程序:initShaders ');
    //创建顶点着色器对象
    var vertexShader = loadShader(gl, gl.VERTEX_SHADER, VSHADER_SOURCE);
    //创建片元着色器对象
    var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, FSHADER_SOURCE);
    if (!vertexShader || !fragmentShader) {
        return null;
    }
	
    //创建程序对象program
    var program = gl.createProgram();
    if (!gl.createProgram()) {
        return null;
    }
    //分配顶点着色器和片元着色器到program
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    //链接program
    gl.linkProgram(program);

    //检查程序对象是否连接成功
    var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!linked) {
        var error = gl.getProgramInfoLog(program);
        console.log('程序对象连接失败: ' + error);
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
        gl.deleteShader(vertexShader);
        return null;
    }
    //返回程序program对象
    initShadersDone = true;
    return program;
}
function loadShader(gl, type, source) {
	console.log('程序:loadShader ');
    // 创建顶点着色器对象
    var shader = gl.createShader(type);
    if (shader == null) {
        console.log('创建着色器失败');
        return null;
    }

    // 引入着色器源代码
    gl.shaderSource(shader, source);

    // 编译着色器
    gl.compileShader(shader);

    // 检查顶是否编译成功
    var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (!compiled) {
        var error = gl.getShaderInfoLog(shader);
        console.log('编译着色器失败: ' + error);
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

//初始化顶点坐标和顶点颜色
function initVertexBuffers(gl, anchor2DList) {
	console.log('程序:initVertexBuffers ');
    const flattenPoints = [];
    anchor2DList.forEach((anchor) => {
        anchor.points.forEach((point) => {
            const { x, y } = point;
            flattenPoints.push(x * 2 - 1, 1 - y * 2);
        });
    });
    var vertices = new Float32Array(flattenPoints);
    var n = flattenPoints.length / 2;

    //创建缓冲区对象
    var buffer = gl.createBuffer();
    //将顶点坐标和顶点颜色信息写入缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    //获取顶点着色器attribute变量a_Position存储地址, 分配缓存并开启
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(a_Position);
    return n;
}
var EDGE_VSHADER_SOURCE = `
  attribute vec2 aPosition; 
  varying vec2 posJudge;

  void main(void) {
    gl_Position = vec4(aPosition.x, aPosition.y, 1.0, 1.0);
    posJudge = aPosition;
  }
`;
var EDGE_FSHADER_SOURCE = `
  precision highp float;
  uniform vec2 rightTopPoint;
  uniform vec2 centerPoint;
  varying vec2 posJudge;

  float box(float x, float y){
    float xc = x - centerPoint.x;
    float yc = y - centerPoint.y;
    vec2 point = vec2(xc, yc);
    float right = rightTopPoint.x;
    float top =  rightTopPoint.y;
    float line_width = 0.01;
    vec2 b1 = 1.0 - step(vec2(right,top), abs(point));
    float outer = b1.x * b1.y;
    vec2 b2 = 1.0 - step(vec2(right-line_width,top-line_width), abs(point));
    float inner = b2.x * b2.y;
    return outer - inner;
  }

  void main(void) {
      if(box(posJudge.x, posJudge.y) == 0.0 ) discard;

      gl_FragColor = vec4(box(posJudge.x, posJudge.y), 0.0, 0.0, 1.0);

  }
`;
function initRectEdgeBuffer(gl, x, y, width, height) {
	console.log('程序:initRectEdgeBuffer ');
    let shaderProgram = gl.program;
    let centerX = x * 2 - 1 + width;
    let centerY = -1 * (y * 2 - 1) - height;
    let right = width;
    let top = height;
    var vertices = [-1, 1, -1, -1, 1, 1, 1, -1];
    var vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    var aPosition = gl.getAttribLocation(shaderProgram, 'aPosition');
    gl.enableVertexAttribArray(aPosition);
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
    var rightTop = [right, top];
    var rightTopLoc = gl.getUniformLocation(shaderProgram, 'rightTopPoint');
    gl.uniform2fv(rightTopLoc, rightTop);
    var centerPoint = [centerX, centerY];
    var centerPointLoc = gl.getUniformLocation(shaderProgram, 'centerPoint');
    gl.uniform2fv(centerPointLoc, centerPoint);
    var length = vertices.length / 2;
    return length;
}
function onDrawRectEdge(gl, x, y, width, height) {
	console.log('程序:onDrawRectEdge ');
    width = Math.round(width * 100) / 100;
    height = Math.round(height * 100) / 100;
    var n = initRectEdgeBuffer(gl, x, y, width, height);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}
export default {
    data() {
        return {
			width: 1,
			height: 1,
			fps: 0,
			memory: 0,
			cpu: 0,
			cameraPosition: 1, //默认1为前置摄像头，0为后置摄像头,
            theme: 'light',
            buttonDisable: true,
        };
    },
    mixins: [yuvBehavior],
    /**
     * 生命周期函数--监听页面加载
     */
    destroyed() {
        initShadersDone = false;
        console.log('页面detached');
        if (uni.offThemeChange) {
            uni.offThemeChange();
        }
    },
    mounted() {
        // 处理小程序 ready 生命周期
        this.$nextTick(() => this.ready());
    },
    methods: {
        ready() {
            console.log('页面准备完全');
            this.setData({
                theme: uni.getSystemInfoSync().theme || 'light'
            });
            if (uni.onThemeChange) {
                uni.onThemeChange(({ theme }) => {
                    this.setData({
                        theme
                    });
                });
            }
        },

        init() {
            this.initGL();
        },

        switchCamera(event) {
            if (this.session.config) {
                const config = this.session.config;
                let pos = Number(event.currentTarget.dataset.value);
                config.cameraPosition = pos;
                this.session.config = config;
                this.setData({
                    cameraPosition: event.currentTarget.dataset.value
                });
            }
        },

        render(frame) {
            var gl = this.gl;
            this.renderGL(frame);
            const camera = frame.camera;

            // 相机
            if (camera) {
                this.camera.matrixAutoUpdate = false;
                this.camera.matrixWorldInverse.fromArray(camera.viewMatrix);
                this.camera.matrixWorld.getInverse(this.camera.matrixWorldInverse);
                const projectionMatrix = camera.getProjectionMatrix(NEAR, FAR);
                this.camera.projectionMatrix.fromArray(projectionMatrix);
                this.camera.projectionMatrixInverse.getInverse(this.camera.projectionMatrix);
            }
            this.renderer.autoClearColor = false;
            this.renderer.render(this.scene, this.camera);
            this.renderer.state.setCullFace(this.THREE.CullFaceNone);
            const anchor2DList = this.anchor2DList;
            if (!anchor2DList || anchor2DList.length <= 0) {
                return;
            } else {
                if (!initShadersDone) {
                    this.vertexProgram = initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
                    this.rectEdgeProgram = initShaders(gl, EDGE_VSHADER_SOURCE, EDGE_FSHADER_SOURCE);
                    if (!this.vertexProgram || !this.rectEdgeProgram) {
                        console.log('初始化着色器失败');
                        return;
                    }
                    console.log('初始化着色器成功');
                }
                gl.useProgram(this.vertexProgram);
                gl.program = this.vertexProgram;
                //初始化顶点坐标和顶点颜色
                var n = initVertexBuffers(gl, anchor2DList);

                //绘制点
                gl.drawArrays(gl.POINTS, 0, n);
                gl.useProgram(this.rectEdgeProgram);
                gl.program = this.rectEdgeProgram;
                for (var i = 0; i < anchor2DList.length; i++) {
                    onDrawRectEdge(gl, anchor2DList[i].origin.x, anchor2DList[i].origin.y, anchor2DList[i].size.width, anchor2DList[i].size.height);
                }
            }
        },

        onTouchEnd() {
            console.log('占位：函数 onTouchEnd 未声明');
        },
			onReady() {
			    wx.createSelectorQuery()
			        .select('#webgl')
			        .node()
			        .exec(res => {
			            this.canvas = res[0].node
			
			            const info = wx.getSystemInfoSync()
			            const pixelRatio = info.pixelRatio
			            const calcSize = (width, height) => {
			                console.log(`canvas size: width = ${width} , height = ${height}`)
			                this.canvas.width = width * pixelRatio
			                this.canvas.height = height * pixelRatio
			                this.setData({
			                    width,
			                    height,
			                })
			            }
			            calcSize(info.windowWidth, info.windowHeight * 0.8)
			            this.initVK()
			        })
			},
			 onUnload() {
		        if (this._texture) {
		            this._texture.dispose();
		            this._texture = null;
		        }
		        if (this.renderer) {
		            this.renderer.dispose();
		            this.renderer = null;
		        }
		        if (this.scene) {
		            this.scene.dispose();
		            this.scene = null;
		        }
		        if (this.camera) {
		            this.camera = null;
		        }
		        if (this.model) {
		            this.model = null;
		        }
		        if (this._insertModel) {
		            this._insertModel = null;
		        }
		        if (this._insertModels) {
		            this._insertModels = null;
		        }
		        if (this.planeBox) {
		            this.planeBox = null;
		        }
		        if (this.mixers) {
		            this.mixers.forEach((mixer) => mixer.uncacheRoot(mixer.getRoot()));
		            this.mixers = null;
		        }
		        if (this.clock) {
		            this.clock = null;
		        }
		        if (this.THREE) {
		            this.THREE = null;
		        }
		        if (this._tempTexture && this._tempTexture.gl) {
		            this._tempTexture.gl.deleteTexture(this._tempTexture);
		            this._tempTexture = null;
		        }
		        if (this._fb && this._fb.gl) {
		            this._fb.gl.deleteFramebuffer(this._fb);
		            this._fb = null;
		        }
		        if (this._program && this._program.gl) {
		            this._program.gl.deleteProgram(this._program);
		            this._program = null;
		        }
		        if (this.canvas) {
		            this.canvas = null;
		        }
		        if (this.gl) {
		            this.gl = null;
		        }
		        if (this.session) {
		            this.session = null;
		        }
		        if (this.anchor2DList) {
		            this.anchor2DList = [];
		        }
		    },
		    initVK() {
		        // 初始化 threejs
		        this.initTHREE();
		
		        // 自定义初始化
		        if (this.init) {
		            this.init();
		        }
		        console.log('this.gl', this.gl);
		        const session = (this.session = uni.createVKSession({
		            track: {
		                plane: {
		                    mode: 3
		                },
		                face: {
		                    mode: 1
		                }
		            },
		            cameraPosition: 1,
		            version: 'v1',
		            gl: this.gl
		        }));
		        session.start((err) => {
		            if (err) {
		                return console.error('VK error: ', err);
		            }
		            console.log('@@@@@@@@ VKSession.version', session.version);
		            const canvas = this.canvas;
		            const calcSize = (width, height, pixelRatio) => {
		                console.log(`canvas size: width = ${width} , height = ${height}`);
		                this.canvas.width = width * pixelRatio;
		                this.canvas.height = height * pixelRatio;
		                this.setData({
		                    width,
		                    height
		                });
		            };
		            session.on('resize', () => {
		                const info = uni.getSystemInfoSync();
		                calcSize(info.windowWidth, info.windowHeight * 0.8, info.pixelRatio);
		            });
		            session.on('addAnchors', (anchors) => {
		                this.anchor2DList = anchors.map((anchor) => ({
		                    points: anchor.points,
		                    origin: anchor.origin,
		                    size: anchor.size
		                }));
		            });
		            session.on('updateAnchors', (anchors) => {
		                this.anchor2DList = [];
		                // 摄像头实时检测人脸的时候 updateAnchors 会在每帧触发，所以性能要求更高，用 gl 画
		                this.anchor2DList = this.anchor2DList.concat(
		                    anchors.map((anchor) => ({
		                        points: anchor.points,
		                        origin: anchor.origin,
		                        size: anchor.size
		                    }))
		                );
		            });
		            session.on('removeAnchors', (anchors) => {
		                this.anchor2DList = [];
		            });
		            this.setData({
		                buttonDisable: false
		            });
		
		            //限制调用帧率
		            let fps = 30;
		            let fpsInterval = 1000 / fps;
		            let last = Date.now();
		
		            // 逐帧渲染
		            const onFrame = (timestamp) => {
		                let now = Date.now();
		                const mill = now - last;
		                // 经过了足够的时间
		                if (mill > fpsInterval) {
		                    last = now - (mill % fpsInterval); //校正当前时间
		                    const frame = session.getVKFrame(canvas.width, canvas.height);
		                    if (frame) {
		                        this.render(frame);
		                    }
		                }
		                session.requestAnimationFrame(onFrame);
		            };
		            session.requestAnimationFrame(onFrame);
		        });
		    },
		    initTHREE() {
		        const THREE = (this.THREE = createScopedThreejs(this.canvas));
		        registerGLTFLoader(THREE);
		
		        // 相机
		        this.camera = new THREE.Camera();
		
		        // 场景
		        const scene = (this.scene = new THREE.Scene());
		
		        // 光源
		        const light1 = new THREE.HemisphereLight(16777215, 4473924); // 半球光
		        light1.position.set(0, 0.2, 0);
		        scene.add(light1);
		        const light2 = new THREE.DirectionalLight(16777215); // 平行光
		        light2.position.set(0, 0.2, 0.1);
		        scene.add(light2);
		
		        // 渲染层
		        const renderer = (this.renderer = new THREE.WebGLRenderer({
		            antialias: true,
		            alpha: true
		        }));
		        renderer.gammaOutput = true;
		        renderer.gammaFactor = 2.2;
		    }
		
	}
		
};
</script>
